// (C) Copyright to Kevin. Applied to MIT LICENSE

#include "CubicBlock.h"

// used for UE_LOG
#include "../../BlockWorld.h"
#include "BlockEngine/ChunkActor.h"
#include "BlockDefinitions.h"

CubicBlock::CubicBlock()
{
}

CubicBlock::~CubicBlock()
{
}

BlockType CubicBlock::GetBlockType()
{
	return BLOCK_TYPE_CUBIC;
}

void CubicBlock::Render(ChunkRenderTask * task, int x, int y, int z, TArray<FVector>* verticies, TArray<int32>* triangles, TArray<FVector2D>* uv0, TArray<FVector2D>* uv1, TArray<FVector2D>* uv2)
{
	Block* relative_blocks[6];
	GetRelativeBlocks(relative_blocks, task, x, y, z);

	// up
	if (!relative_blocks[DIRECTION_UP] || (relative_blocks[DIRECTION_UP] && relative_blocks[DIRECTION_UP]->IsTransparent())) {
		// we convert back to UE4's Z=up system. 
		int start_index = verticies->Add(FVector(x, z, y + 1)); // left down
		verticies->Add(FVector(x, z + 1, y + 1)); // top left
		verticies->Add(FVector(x + 1, z + 1, y + 1)); // top right
		verticies->Add(FVector(x + 1, z, y + 1)); // down right

		triangles->Add(start_index);
		triangles->Add(start_index + 1);
		triangles->Add(start_index + 2);
		triangles->Add(start_index);
		triangles->Add(start_index + 2);
		triangles->Add(start_index + 3);

		FString texture_color_name = GetTexture(DIRECTION_UP, TEXTURE_TYPE_COLOR);
		FString texture_normal_name = GetTexture(DIRECTION_UP, TEXTURE_TYPE_NORMAL);
		FString texture_specular_name = GetTexture(DIRECTION_UP, TEXTURE_TYPE_SPECULAR);
		TextureInfo* texture_color = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_COLOR);
		TextureInfo* texture_normal = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_NORMAL);
		TextureInfo* texture_specular = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_SPECULAR);
		uv0->Add(FVector2D(texture_color->start_x, texture_color->end_y));
		uv0->Add(FVector2D(texture_color->start_x, texture_color->start_y));
		uv0->Add(FVector2D(texture_color->end_x, texture_color->start_y));
		uv0->Add(FVector2D(texture_color->end_x, texture_color->end_y));
		uv1->Add(FVector2D(texture_normal->start_x, texture_normal->end_y));
		uv1->Add(FVector2D(texture_normal->start_x, texture_normal->start_y));
		uv1->Add(FVector2D(texture_normal->end_x, texture_normal->start_y));
		uv1->Add(FVector2D(texture_normal->end_x, texture_normal->end_y));
		uv2->Add(FVector2D(texture_specular->start_x, texture_specular->end_y));
		uv2->Add(FVector2D(texture_specular->start_x, texture_specular->start_y));
		uv2->Add(FVector2D(texture_specular->end_x, texture_specular->start_y));
		uv2->Add(FVector2D(texture_specular->end_x, texture_specular->end_y));
	}

	// down
	if (!relative_blocks[DIRECTION_DOWN] || (relative_blocks[DIRECTION_DOWN] && relative_blocks[DIRECTION_DOWN]->IsTransparent())) {
		// we convert back to UE4's Z=up system. 
		int start_index = verticies->Add(FVector(x, z, y)); // top left
		verticies->Add(FVector(x + 1, z, y)); // top right
		verticies->Add(FVector(x + 1, z + 1, y)); // down right
		verticies->Add(FVector(x, z + 1, y)); // down left

		triangles->Add(start_index);
		triangles->Add(start_index + 1);
		triangles->Add(start_index + 2);
		triangles->Add(start_index);
		triangles->Add(start_index + 2);
		triangles->Add(start_index + 3);

		FString texture_color_name = GetTexture(DIRECTION_DOWN, TEXTURE_TYPE_COLOR);
		FString texture_normal_name = GetTexture(DIRECTION_DOWN, TEXTURE_TYPE_NORMAL);
		FString texture_specular_name = GetTexture(DIRECTION_DOWN, TEXTURE_TYPE_SPECULAR);
		TextureInfo* texture_color = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_COLOR);
		TextureInfo* texture_normal = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_NORMAL);
		TextureInfo* texture_specular = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_SPECULAR);
		uv0->Add(FVector2D(texture_color->start_x, texture_color->start_y));
		uv0->Add(FVector2D(texture_color->end_x, texture_color->start_y));
		uv0->Add(FVector2D(texture_color->end_x, texture_color->end_y));
		uv0->Add(FVector2D(texture_color->start_x, texture_color->end_y));
		uv1->Add(FVector2D(texture_normal->start_x, texture_normal->start_y));
		uv1->Add(FVector2D(texture_normal->end_x, texture_normal->start_y));
		uv1->Add(FVector2D(texture_normal->end_x, texture_normal->end_y));
		uv1->Add(FVector2D(texture_normal->start_x, texture_normal->end_y));
		uv2->Add(FVector2D(texture_specular->start_x, texture_specular->start_y));
		uv2->Add(FVector2D(texture_specular->end_x, texture_specular->start_y));
		uv2->Add(FVector2D(texture_specular->end_x, texture_specular->end_y));
		uv2->Add(FVector2D(texture_specular->start_x, texture_specular->end_y));
	}

	// x+
	if (!relative_blocks[DIRECTION_X_PLUS] || (relative_blocks[DIRECTION_X_PLUS] && relative_blocks[DIRECTION_X_PLUS]->IsTransparent())) {
		// we convert back to UE4's Z=up system. 
		int start_index = verticies->Add(FVector(x + 1, z, y)); // down left
		verticies->Add(FVector(x + 1, z, y + 1)); // top left
		verticies->Add(FVector(x + 1, z + 1, y + 1)); // top right
		verticies->Add(FVector(x + 1, z + 1, y)); // down right

		triangles->Add(start_index);
		triangles->Add(start_index + 1);
		triangles->Add(start_index + 2);
		triangles->Add(start_index);
		triangles->Add(start_index + 2);
		triangles->Add(start_index + 3);

		FString texture_color_name = GetTexture(DIRECTION_X_PLUS, TEXTURE_TYPE_COLOR);
		FString texture_normal_name = GetTexture(DIRECTION_X_PLUS, TEXTURE_TYPE_NORMAL);
		FString texture_specular_name = GetTexture(DIRECTION_X_PLUS, TEXTURE_TYPE_SPECULAR);
		TextureInfo* texture_color = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_COLOR);
		TextureInfo* texture_normal = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_NORMAL);
		TextureInfo* texture_specular = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_SPECULAR);
		uv0->Add(FVector2D(texture_color->start_x, texture_color->start_y));
		uv0->Add(FVector2D(texture_color->start_x, texture_color->end_y));
		uv0->Add(FVector2D(texture_color->end_x, texture_color->end_y));
		uv0->Add(FVector2D(texture_color->end_x, texture_color->start_y));
		uv1->Add(FVector2D(texture_normal->start_x, texture_normal->start_y));
		uv1->Add(FVector2D(texture_normal->start_x, texture_normal->end_y));
		uv1->Add(FVector2D(texture_normal->end_x, texture_normal->end_y));
		uv1->Add(FVector2D(texture_normal->end_x, texture_normal->start_y));
		uv2->Add(FVector2D(texture_specular->start_x, texture_specular->start_y));
		uv2->Add(FVector2D(texture_specular->start_x, texture_specular->end_y));
		uv2->Add(FVector2D(texture_specular->end_x, texture_specular->end_y));
		uv2->Add(FVector2D(texture_specular->end_x, texture_specular->start_y));
	}

	// x-
	if (!relative_blocks[DIRECTION_X_MINUS] || (relative_blocks[DIRECTION_X_MINUS] && relative_blocks[DIRECTION_X_MINUS]->IsTransparent())) {
		// we convert back to UE4's Z=up system. 
		int start_index = verticies->Add(FVector(x, z + 1, y)); // down left
		verticies->Add(FVector(x, z + 1, y + 1)); // top left
		verticies->Add(FVector(x, z, y + 1)); // top right
		verticies->Add(FVector(x, z, y)); // down right

		triangles->Add(start_index);
		triangles->Add(start_index + 1);
		triangles->Add(start_index + 2);
		triangles->Add(start_index);
		triangles->Add(start_index + 2);
		triangles->Add(start_index + 3);

		FString texture_color_name = GetTexture(DIRECTION_X_MINUS, TEXTURE_TYPE_COLOR);
		FString texture_normal_name = GetTexture(DIRECTION_X_MINUS, TEXTURE_TYPE_NORMAL);
		FString texture_specular_name = GetTexture(DIRECTION_X_MINUS, TEXTURE_TYPE_SPECULAR);
		TextureInfo* texture_color = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_COLOR);
		TextureInfo* texture_normal = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_NORMAL);
		TextureInfo* texture_specular = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_SPECULAR);
		uv0->Add(FVector2D(texture_color->start_x, texture_color->start_y));
		uv0->Add(FVector2D(texture_color->start_x, texture_color->end_y));
		uv0->Add(FVector2D(texture_color->end_x, texture_color->end_y));
		uv0->Add(FVector2D(texture_color->end_x, texture_color->start_y));
		uv1->Add(FVector2D(texture_normal->start_x, texture_normal->start_y));
		uv1->Add(FVector2D(texture_normal->start_x, texture_normal->end_y));
		uv1->Add(FVector2D(texture_normal->end_x, texture_normal->end_y));
		uv1->Add(FVector2D(texture_normal->end_x, texture_normal->start_y));
		uv2->Add(FVector2D(texture_specular->start_x, texture_specular->start_y));
		uv2->Add(FVector2D(texture_specular->start_x, texture_specular->end_y));
		uv2->Add(FVector2D(texture_specular->end_x, texture_specular->end_y));
		uv2->Add(FVector2D(texture_specular->end_x, texture_specular->start_y));
	}

	// z+
	if (!relative_blocks[DIRECTION_Z_PLUS] || (relative_blocks[DIRECTION_Z_PLUS] && relative_blocks[DIRECTION_Z_PLUS]->IsTransparent())) {
		// we convert back to UE4's Z=up system. 
		int start_index = verticies->Add(FVector(x + 1, z + 1, y)); // down left
		verticies->Add(FVector(x + 1, z + 1, y + 1)); // top left
		verticies->Add(FVector(x, z + 1, y + 1)); // top right
		verticies->Add(FVector(x, z + 1, y)); // down right

		triangles->Add(start_index);
		triangles->Add(start_index + 1);
		triangles->Add(start_index + 2);
		triangles->Add(start_index);
		triangles->Add(start_index + 2);
		triangles->Add(start_index + 3);

		FString texture_color_name = GetTexture(DIRECTION_Z_PLUS, TEXTURE_TYPE_COLOR);
		FString texture_normal_name = GetTexture(DIRECTION_Z_PLUS, TEXTURE_TYPE_NORMAL);
		FString texture_specular_name = GetTexture(DIRECTION_Z_PLUS, TEXTURE_TYPE_SPECULAR);
		TextureInfo* texture_color = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_COLOR);
		TextureInfo* texture_normal = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_NORMAL);
		TextureInfo* texture_specular = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_SPECULAR);
		uv0->Add(FVector2D(texture_color->start_x, texture_color->start_y));
		uv0->Add(FVector2D(texture_color->start_x, texture_color->end_y));
		uv0->Add(FVector2D(texture_color->end_x, texture_color->end_y));
		uv0->Add(FVector2D(texture_color->end_x, texture_color->start_y));
		uv1->Add(FVector2D(texture_normal->start_x, texture_normal->start_y));
		uv1->Add(FVector2D(texture_normal->start_x, texture_normal->end_y));
		uv1->Add(FVector2D(texture_normal->end_x, texture_normal->end_y));
		uv1->Add(FVector2D(texture_normal->end_x, texture_normal->start_y));
		uv2->Add(FVector2D(texture_specular->start_x, texture_specular->start_y));
		uv2->Add(FVector2D(texture_specular->start_x, texture_specular->end_y));
		uv2->Add(FVector2D(texture_specular->end_x, texture_specular->end_y));
		uv2->Add(FVector2D(texture_specular->end_x, texture_specular->start_y));
	}

	// z-
	if (!relative_blocks[DIRECTION_Z_MINUS] || (relative_blocks[DIRECTION_Z_MINUS] && relative_blocks[DIRECTION_Z_MINUS]->IsTransparent())) {
		// we convert back to UE4's Z=up system. 
		int start_index = verticies->Add(FVector(x, z, y)); // down left
		verticies->Add(FVector(x, z, y + 1)); // top left
		verticies->Add(FVector(x + 1, z, y + 1)); // top right
		verticies->Add(FVector(x + 1, z, y)); // down right

		triangles->Add(start_index);
		triangles->Add(start_index + 1);
		triangles->Add(start_index + 2);
		triangles->Add(start_index);
		triangles->Add(start_index + 2);
		triangles->Add(start_index + 3);

		FString texture_color_name = GetTexture(DIRECTION_Z_MINUS, TEXTURE_TYPE_COLOR);
		FString texture_normal_name = GetTexture(DIRECTION_Z_MINUS, TEXTURE_TYPE_NORMAL);
		FString texture_specular_name = GetTexture(DIRECTION_Z_MINUS, TEXTURE_TYPE_SPECULAR);
		TextureInfo* texture_color = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_COLOR);
		TextureInfo* texture_normal = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_NORMAL);
		TextureInfo* texture_specular = task->texture_manager->GetTexture(texture_color_name, TEXTURE_TYPE_SPECULAR);
		uv0->Add(FVector2D(texture_color->start_x, texture_color->start_y));
		uv0->Add(FVector2D(texture_color->start_x, texture_color->end_y));
		uv0->Add(FVector2D(texture_color->end_x, texture_color->end_y));
		uv0->Add(FVector2D(texture_color->end_x, texture_color->start_y));
		uv1->Add(FVector2D(texture_normal->start_x, texture_normal->start_y));
		uv1->Add(FVector2D(texture_normal->start_x, texture_normal->end_y));
		uv1->Add(FVector2D(texture_normal->end_x, texture_normal->end_y));
		uv1->Add(FVector2D(texture_normal->end_x, texture_normal->start_y));
		uv2->Add(FVector2D(texture_specular->start_x, texture_specular->start_y));
		uv2->Add(FVector2D(texture_specular->start_x, texture_specular->end_y));
		uv2->Add(FVector2D(texture_specular->end_x, texture_specular->end_y));
		uv2->Add(FVector2D(texture_specular->end_x, texture_specular->start_y));
	}
}

void CubicBlock::GetRelativeBlocks(Block** return_value, ChunkRenderTask* task, int x, int y, int z)
{
	AChunkActor* source = task->GetSourceChunk();
	ChunkData* source_chunk = source->GetChunkData();
	ChunkData* relative_chunks[6] = {
		source->neighbors[DIRECTION_UP] ? source->neighbors[DIRECTION_UP]->GetChunkData() : NULL,
		source->neighbors[DIRECTION_DOWN] ? source->neighbors[DIRECTION_DOWN]->GetChunkData() : NULL,
		source->neighbors[DIRECTION_X_PLUS] ? source->neighbors[DIRECTION_X_PLUS]->GetChunkData() : NULL,
		source->neighbors[DIRECTION_X_MINUS] ? source->neighbors[DIRECTION_X_MINUS]->GetChunkData() : NULL,
		source->neighbors[DIRECTION_Z_PLUS] ? source->neighbors[DIRECTION_Z_PLUS]->GetChunkData() : NULL,
		source->neighbors[DIRECTION_Z_MINUS] ? source->neighbors[DIRECTION_Z_MINUS]->GetChunkData() : NULL
	};

	// upper
	if (y == 15) {
		if (relative_chunks[DIRECTION_UP]) {
			int index = BlockIndex(x, 0, z);
			uint16 id = relative_chunks[DIRECTION_UP]->id[index];
			Block* def = task->block_manager->Find(id);
			if (def) {
				return_value[DIRECTION_UP] = def;
			} else {
				return_value[DIRECTION_UP] = NULL;
				UE_LOG(BlockEngine, Log, TEXT("Error! Failed to find block with id [%d] when getting from upper chunk. "), id);
			}
		} else {
			return_value[DIRECTION_UP] = NULL;
		}
	} else {
		int index = BlockIndex(x, y + 1, z);
		uint16 id = source_chunk->id[index];
		Block* def = task->block_manager->Find(id);
		if (def) {
			return_value[DIRECTION_UP] = def;
		} else {
			return_value[DIRECTION_UP] = NULL;
			UE_LOG(BlockEngine, Log, TEXT("Error! Failed to find block with id [%d] within the chunk. "), id);
		}
	}

	// lower
	if (y == 0) {
		if (relative_chunks[DIRECTION_DOWN]) {
			int index = BlockIndex(x, 15, z);
			uint16 id = relative_chunks[DIRECTION_DOWN]->id[index];
			Block* def = task->block_manager->Find(id);
			if (def) {
				return_value[DIRECTION_DOWN] = def;
			} else {
				return_value[DIRECTION_DOWN] = NULL;
				UE_LOG(BlockEngine, Log, TEXT("Error! Failed to find block with id [%d] when getting from upper chunk. "), id);
			}
		} else {
			return_value[DIRECTION_DOWN] = NULL;
		}
	} else {
		int index = BlockIndex(x, y - 1, z);
		uint16 id = source_chunk->id[index];
		Block* def = task->block_manager->Find(id);
		if (def) {
			return_value[DIRECTION_DOWN] = def;
		} else {
			return_value[DIRECTION_DOWN] = NULL;
			UE_LOG(BlockEngine, Log, TEXT("Error! Failed to find block with id [%d] within the chunk. "), id);
		}
	}

	// x+
	if (x == 15) {
		if (relative_chunks[DIRECTION_X_PLUS]) {
			int index = BlockIndex(0, y, z);
			uint16 id = relative_chunks[DIRECTION_X_PLUS]->id[index];
			Block* def = task->block_manager->Find(id);
			if (def) {
				return_value[DIRECTION_X_PLUS] = def;
			} else {
				return_value[DIRECTION_X_PLUS] = NULL;
				UE_LOG(BlockEngine, Log, TEXT("Error! Failed to find block with id [%d] when getting from upper chunk. "), id);
			}
		} else {
			return_value[DIRECTION_X_PLUS] = NULL;
		}
	} else {
		int index = BlockIndex(x + 1, y, z);
		uint16 id = source_chunk->id[index];
		Block* def = task->block_manager->Find(id);
		if (def) {
			return_value[DIRECTION_X_PLUS] = def;
		} else {
			return_value[DIRECTION_X_PLUS] = NULL;
			UE_LOG(BlockEngine, Log, TEXT("Error! Failed to find block with id [%d] within the chunk. "), id);
		}
	}

	// x-
	if (x == 0) {
		if (relative_chunks[DIRECTION_X_MINUS]) {
			int index = BlockIndex(15, y, z);
			uint16 id = relative_chunks[DIRECTION_X_MINUS]->id[index];
			Block* def = task->block_manager->Find(id);
			if (def) {
				return_value[DIRECTION_X_MINUS] = def;
			}
			else {
				return_value[DIRECTION_X_MINUS] = NULL;
				UE_LOG(BlockEngine, Log, TEXT("Error! Failed to find block with id [%d] when getting from upper chunk. "), id);
			}
		} else {
			return_value[DIRECTION_X_MINUS] = NULL;
		}
	} else {
		int index = BlockIndex(x - 1, y, z);
		uint16 id = source_chunk->id[index];
		Block* def = task->block_manager->Find(id);
		if (def) {
			return_value[DIRECTION_X_MINUS] = def;
		} else {
			return_value[DIRECTION_X_MINUS] = NULL;
			UE_LOG(BlockEngine, Log, TEXT("Error! Failed to find block with id [%d] within the chunk. "), id);
		}
	}

	// z+
	if (z == 15) {
		if (relative_chunks[DIRECTION_Z_PLUS]) {
			int index = BlockIndex(x, y, 0);
			uint16 id = relative_chunks[DIRECTION_Z_PLUS]->id[index];
			Block* def = task->block_manager->Find(id);
			if (def) {
				return_value[DIRECTION_Z_PLUS] = def;
			} else {
				return_value[DIRECTION_Z_PLUS] = NULL;
				UE_LOG(BlockEngine, Log, TEXT("Error! Failed to find block with id [%d] when getting from upper chunk. "), id);
			}
		} else {
			return_value[DIRECTION_Z_PLUS] = NULL;
		}
	} else {
		int index = BlockIndex(x, y, z + 1);
		uint16 id = source_chunk->id[index];
		Block* def = task->block_manager->Find(id);
		if (def) {
			return_value[DIRECTION_Z_PLUS] = def;
		} else {
			return_value[DIRECTION_Z_PLUS] = NULL;
			UE_LOG(BlockEngine, Log, TEXT("Error! Failed to find block with id [%d] within the chunk. "), id);
		}
	}

	// z-
	if (z == 0) {
		if (relative_chunks[DIRECTION_Z_MINUS]) {
			int index = BlockIndex(x, y, 15);
			uint16 id = relative_chunks[DIRECTION_Z_MINUS]->id[index];
			Block* def = task->block_manager->Find(id);
			if (def) {
				return_value[DIRECTION_Z_MINUS] = def;
			}
			else {
				return_value[DIRECTION_Z_MINUS] = NULL;
				UE_LOG(BlockEngine, Log, TEXT("Error! Failed to find block with id [%d] when getting from upper chunk. "), id);
			}
		}
		else {
			return_value[DIRECTION_Z_MINUS] = NULL;
		}
	} else {
		int index = BlockIndex(x, y, z - 1);
		uint16 id = source_chunk->id[index];
		Block* def = task->block_manager->Find(id);
		if (def) {
			return_value[DIRECTION_Z_MINUS] = def;
		}
		else {
			return_value[DIRECTION_Z_MINUS] = NULL;
			UE_LOG(BlockEngine, Log, TEXT("Error! Failed to find block with id [%d] within the chunk. "), id);
		}
	}
}